package com.hwtx.form.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtil {

    private static final Logger log = LoggerFactory.getLogger(ClassUtil.class);


    /**
     * 是否是bases子类或实现了bases接口(满足其中一个)
     *
     * @param clazz 类
     * @param bases 父类或接口
     * @return boolean
     */
    public static boolean isInSub(Class<?> clazz, Class<?>... bases) {
        if (null == bases || bases.length == 0) {
            return true;
        }
        for (Class<?> base : bases) {
            if (base.isAssignableFrom(clazz)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取某包下所有类
     *
     * @param packageName  包名
     * @param childPackage 是否遍历子包
     * @return 类的完整名称
     * @throws UnsupportedEncodingException
     */
    private static List<String> getClassNames(String packageName, boolean childPackage) throws IOException {
        List<String> fileNames = new ArrayList<>();
        log.warn("[正在加载本地类][package:{}]", packageName);
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageName.replace(".", "/");
        Enumeration<URL> urls = loader.getResources(packagePath);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url == null)
                continue;
            String type = url.getProtocol();
            if (type.equals("file")) {
                fileNames.addAll(getClassNameListFromFile(url.getPath(), childPackage));
            } else if (type.equals("jar")) {
                fileNames.addAll(getClassNameListFromJar(url.getPath(), childPackage));
            }
        }
        return fileNames;
    }

    /**
     * 从项目文件获取某包下所有类
     *
     * @param filePath     文件路径
     *                     类名集合
     * @param childPackage 是否遍历子包
     * @return 类的完整名称
     */
    private static List<String> getClassNameListFromFile(String filePath, boolean childPackage) {
        List<String> myClassName = new ArrayList<>();
        // filePath = UrlDecode.getURLDecode(filePath);
        File file = new File(filePath);
        File[] childFiles = file.listFiles();
        if (childFiles == null)
            return myClassName;
        for (File childFile : childFiles) {
            if (childFile.isDirectory()) {
                if (childPackage) {
                    myClassName.addAll(getClassNameListFromFile(childFile.getPath(), childPackage));
                }
            } else {
                String childFilePath = childFile.getPath();
                if (childFilePath.endsWith(".class") && !childFilePath.contains("$")) {
                    String c = childFilePath.split("classes")[1].replace(".class", "");
                    c = c.replace("\\", ".").replace("//", ".");
                    if (c.startsWith(".")) {
                        c = c.substring(1);
                    }
                    myClassName.add(c);
                }
            }
        }
        return myClassName;
    }

    /**
     * 从jar获取某包下所有类
     *
     * @param jarPath      jar文件路径
     * @param childPackage 是否遍历子包
     * @return 类的完整名称
     */
    private static List<String> getClassNameListFromJar(String jarPath, boolean childPackage) {
        List<String> names = new ArrayList<>();
        String[] jarInfo = jarPath.split("!");
        String jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/"));
        String packagePath = jarInfo[1].substring(1);
        try {
            JarFile jarFile = new JarFile(jarFilePath);
            Enumeration<JarEntry> entrys = jarFile.entries();
            while (entrys.hasMoreElements()) {
                JarEntry jarEntry = entrys.nextElement();
                String entryName = jarEntry.getName();
                if (entryName.endsWith(".class") && !entryName.contains("$")) {
                    if (childPackage) {
                        if (entryName.startsWith(packagePath)) {
                            entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
                            names.add(entryName);
                        }
                    } else {
                        int index = entryName.lastIndexOf("/");
                        String myPackagePath;
                        if (index != -1) {
                            myPackagePath = entryName.substring(0, index);
                        } else {
                            myPackagePath = entryName;
                        }
                        if (myPackagePath.equals(packagePath)) {
                            entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
                            names.add(entryName);
                        }
                    }
                }
            }
        } catch (Exception e) {
        }
        return names;
    }

    /**
     * 反射类注解的属性值 在不确定具体注解与属性的情况下使用
     * 注解名与属性名不区分大小写
     * *表示任意字符
     *
     * @param target     类
     * @param annotation 注解类名 如: *, Table*
     * @param field      属性名 如: *, value, name, *package*
     * @param qty        最多取几个值 -1:不限制
     * @return List
     */
    public static List<Object> parseAnnotationFieldValues(Class target, String annotation, String field, int qty) {
        Annotation[] annotations = target.getAnnotations();
        return parseAnnotationFieldValues(annotations, annotation, field, qty);
    }

    /**
     * 反射属性target上的注解, 获取注解上指定的属性值
     * 注解名与属性名不区分大小写
     * *表示任意字符
     *
     * @param target     类的属性
     * @param annotation 注解类名 支持模糊匹配 如: *, Table*
     * @param field      注解的属性名 如: *, value, name, *package*
     * @param qty        最多取几个值 -1:不限制
     * @return List
     */
    public static List<Object> parseAnnotationFieldValues(Field target, String annotation, String field, int qty) {
        Annotation[] annotations = target.getAnnotations();
        return parseAnnotationFieldValues(annotations, annotation, field, qty);
    }

    private static List<Object> parseAnnotationFieldValues(Annotation[] annotations, String annotation, String field, int qty) {
        List<Object> list = new ArrayList<>();
        for (Annotation an : annotations) {
            String name = an.annotationType().getSimpleName();
            if (!match(name, annotation)) {
                continue;//注解名不匹配
            }
            Method[] methods = an.annotationType().getMethods();
            for (Method method : methods) {
                name = method.getName();
                if (!match(name, field)) {
                    continue;//属性名不匹配
                }
                try {
                    Object value = method.invoke(an);
                    if (value instanceof Object[] values) {
                        for (Object v : values) {
                            list.add(v);
                            if (qty > 0 && list.size() >= qty) {
                                return list;
                            }
                        }
                    } else {
                        list.add(value);
                    }
                } catch (Exception e) {
                }
            }

        }
        return list;
    }

    private static boolean match(String value, String regex) {
        regex = regex.replace("*", ".*").toUpperCase();
        return value.toUpperCase().matches(regex);
    }

    /**
     * 提取类及父类的所有属性
     *
     * @param clazz   clazz
     * @param statics 是否返回静态属性
     * @param finals  是否返回final属性
     * @return List
     */
    public static List<Field> getFields(Class<?> clazz, boolean statics, boolean finals) {
        List<Field> fields = new ArrayList<>();
        while (null != clazz) {
            Field[] tmp = clazz.getDeclaredFields();
            for (Field field : tmp) {
                if (!statics && Modifier.isStatic(field.getModifiers())) {
                    continue;
                }
                if (!finals && Modifier.isFinal(field.getModifiers())) {
                    continue;
                }
                fields.add(field);
            }
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    public static List<Field> getFields(Class<?> clazz) {
        return getFields(clazz, true, true);
    }

    public static boolean hasField(Class<?> clazz, String fieldName) {
        try {
            clazz.getDeclaredField(fieldName);
            return true;
        } catch (NoSuchFieldException ignored) {

        }
        return false;
    }

    public static Field getField(Class<?> clazz, String name, boolean recursion) {
        Field field = null;
        try {
            field = clazz.getField(name);
        } catch (Exception e) {
        }
        if (null == field) {
            try {
                field = clazz.getDeclaredField(name);
            } catch (Exception e) {

            }
        }
        // 递归父类
        if (null == field && recursion) {
            clazz = clazz.getSuperclass();
            if (null != clazz) {
                field = getField(clazz, name);
            }
        }
        return field;
    }

    public static Field getField(Class<?> clazz, String name) {
        return getField(clazz, name, true);
    }

    /**
     * 对象类型<br/>
     * int[] > int[]<br/>
     * Integer[] > java.long.Integer[]<br/>
     *
     * @param obj 对象
     * @return 类型
     */
    public static String type(Object obj) {
        String type = null;
        if (null != obj) {
            type = type(obj.getClass());
        }
        return type;
    }

    public static String type(Class clazz) {
        String type = null;
        if (null != clazz) {
            if (clazz.isArray()) {
                type = clazz.getComponentType().getName() + "[]";
            } else {
                type = clazz.getName();
            }
        }
        return type;
    }

}
